/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "camera.h"
#include "scenemanager.h"
#include "matrix4.h"
#include "resourcemanager.h"
#include "material.h"
#include "graphic.h"

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	/**********************************************************************************************************/
	Camera::Camera(const std::string& name,SceneManager* sm)
		:m_name(name)
		,m_sceneManager(sm)
		,m_viewMatrix(Matrix4<REAL>::IDENTITY)
		,m_projMatrix(Matrix4<REAL>::IDENTITY)
		,m_polygonMode(PM_SOLID)
		,m_renderedFaces(0)
		,m_planes(6)
		,m_debugMode(false)
	{
		m_renderable = RefPtr<CameraRenderable,WeakPtrExtension>(new CameraRenderable(this));
	}
	/**********************************************************************************************************/
	Camera::~Camera()
	{
	}
	/**********************************************************************************************************/
	const std::string& Camera::GetName()
	{
		return m_name;
	}
	/**********************************************************************************************************/
	void Camera::SetPolygonMode(PolygonMode sd)
	{
		m_polygonMode = sd;
	}
	/**********************************************************************************************************/
	const Vector3<REAL>& Camera::GetPosition()const
	{
		return m_position;
	}
	/**********************************************************************************************************/
	void Camera::LookAt(const Vector3<REAL>& pos, const Vector3<REAL>& dir, const Vector3<REAL>& up)
	{
		//right hand
		m_position = pos;
		m_dir = pos - dir;
		m_dir.Normalise();
		m_right = up.CrossProduct(m_dir);
		m_right.Normalise();
		m_up = up;
		m_up.Normalise();

		Vector3<REAL> n = m_dir;
		Vector3<REAL> u = m_right;
		Vector3<REAL> v = n.CrossProduct(u);
		v.Normalise();

		Vector3<REAL> d(-pos.DotProduct(u),-pos.DotProduct(v),-pos.DotProduct(n));
		m_viewMatrix[0][0] = u.x; m_viewMatrix[0][1] = u.y; m_viewMatrix[0][2] = u.z; m_viewMatrix[0][3] = d.x;
		m_viewMatrix[1][0] = v.x; m_viewMatrix[1][1] = v.y; m_viewMatrix[1][2] = v.z; m_viewMatrix[1][3] = d.y;
		m_viewMatrix[2][0] = n.x; m_viewMatrix[2][1] = n.y; m_viewMatrix[2][2] = n.z; m_viewMatrix[2][3] = d.z;
		m_viewMatrix[3][0] = 0;   m_viewMatrix[3][1] = 0;   m_viewMatrix[3][2] = 0;   m_viewMatrix[3][3] = 1;

		PlanesUpdate();
	}
	/**********************************************************************************************************/
	void Camera::LookAt()
	{
		//right hand

		Vector3<REAL> n = m_dir;
		n.Normalise();
		Vector3<REAL> u = m_right;
		u.Normalise();
		Vector3<REAL> v = n.CrossProduct(u);
		v.Normalise();

		Vector3<REAL> d(-m_position.DotProduct(u),-m_position.DotProduct(v),-m_position.DotProduct(n));
		m_viewMatrix[0][0] = u.x; m_viewMatrix[0][1] = u.y; m_viewMatrix[0][2] = u.z; m_viewMatrix[0][3] = d.x;
		m_viewMatrix[1][0] = v.x; m_viewMatrix[1][1] = v.y; m_viewMatrix[1][2] = v.z; m_viewMatrix[1][3] = d.y;
		m_viewMatrix[2][0] = n.x; m_viewMatrix[2][1] = n.y; m_viewMatrix[2][2] = n.z; m_viewMatrix[2][3] = d.z;
		m_viewMatrix[3][0] = 0;   m_viewMatrix[3][1] = 0;   m_viewMatrix[3][2] = 0;   m_viewMatrix[3][3] = 1;

		PlanesUpdate();
	}
	/**********************************************************************************************************/
	void Camera::SetPerspective(REAL viewAngle, REAL aspect, REAL neard, REAL fard)
	{

		//right hand
		REAL fov = (PI/180) * viewAngle;
		REAL yScale = 1.0/tan(fov/2);
	    REAL xScale = yScale / aspect;

		m_projMatrix = Matrix4<REAL>(xScale,0,0,0,
									 0,yScale,0,0,
									 0,0,fard/(neard - fard),neard * fard / (neard - fard),
									 0,0,-1,0);

		PlanesUpdate();
	}
	/**********************************************************************************************************/
	void Camera::RenderScene(RenderTarget* renderTarget)
	{
		m_sceneManager->RenderScene(this,renderTarget);
	}
	/**********************************************************************************************************/
	unsigned int Camera::GetNumRenderedFaces(void) const
	{
		return m_renderedFaces;
	}
	/**********************************************************************************************************/
	void Camera::NotifyRenderedFaces(unsigned int faces)const
	{
		m_renderedFaces = faces;
	}
	/**********************************************************************************************************/
	const Matrix4<REAL>& Camera::GetViewMatrix() const
	{
		return m_viewMatrix;
	}
	/**********************************************************************************************************/
	const Matrix4<REAL>& Camera::GetProjMatrix() const
	{
		return m_projMatrix;
	}
	/**********************************************************************************************************/
	void Camera::PlanesUpdate()
	{
		if(m_debugMode)
			return;

		Matrix4<REAL> clipMatr(m_projMatrix * m_viewMatrix);

		//calculate planes
		m_planes[FRUSTUM_PLANE_LEFT].a = clipMatr[3][0] + clipMatr[0][0];
		m_planes[FRUSTUM_PLANE_LEFT].b = clipMatr[3][1] + clipMatr[0][1];
		m_planes[FRUSTUM_PLANE_LEFT].c = clipMatr[3][2] + clipMatr[0][2];
		m_planes[FRUSTUM_PLANE_LEFT].d = clipMatr[3][3] + clipMatr[0][3];

		m_planes[FRUSTUM_PLANE_RIGHT].a = clipMatr[3][0] - clipMatr[0][0];
		m_planes[FRUSTUM_PLANE_RIGHT].b = clipMatr[3][1] - clipMatr[0][1];
		m_planes[FRUSTUM_PLANE_RIGHT].c = clipMatr[3][2] - clipMatr[0][2];
		m_planes[FRUSTUM_PLANE_RIGHT].d = clipMatr[3][3] - clipMatr[0][3];

		m_planes[FRUSTUM_PLANE_TOP].a = clipMatr[3][0] - clipMatr[1][0];
		m_planes[FRUSTUM_PLANE_TOP].b = clipMatr[3][1] - clipMatr[1][1];
		m_planes[FRUSTUM_PLANE_TOP].c = clipMatr[3][2] - clipMatr[1][2];
		m_planes[FRUSTUM_PLANE_TOP].d = clipMatr[3][3] - clipMatr[1][3];

		m_planes[FRUSTUM_PLANE_BOTTOM].a = clipMatr[3][0] + clipMatr[1][0];
		m_planes[FRUSTUM_PLANE_BOTTOM].b = clipMatr[3][1] + clipMatr[1][1];
		m_planes[FRUSTUM_PLANE_BOTTOM].c = clipMatr[3][2] + clipMatr[1][2];
		m_planes[FRUSTUM_PLANE_BOTTOM].d = clipMatr[3][3] + clipMatr[1][3];

		m_planes[FRUSTUM_PLANE_NEAR].a = clipMatr[3][0] + clipMatr[2][0];
		m_planes[FRUSTUM_PLANE_NEAR].b = clipMatr[3][1] + clipMatr[2][1];
		m_planes[FRUSTUM_PLANE_NEAR].c = clipMatr[3][2] + clipMatr[2][2];
		m_planes[FRUSTUM_PLANE_NEAR].d = clipMatr[3][3] + clipMatr[2][3];

		m_planes[FRUSTUM_PLANE_FAR].a = clipMatr[3][0] - clipMatr[2][0];
		m_planes[FRUSTUM_PLANE_FAR].b = clipMatr[3][1] - clipMatr[2][1];
		m_planes[FRUSTUM_PLANE_FAR].c = clipMatr[3][2] - clipMatr[2][2];
		m_planes[FRUSTUM_PLANE_FAR].d = clipMatr[3][3] - clipMatr[2][3];


		for(int i = 0; i < 6; ++i)
		{
			m_planes[i].Normalize();
		}
	}
	/**********************************************************************************************************/
	Visibility Camera::GetVisibility(const AABB& bbox)const
	{
		bool all_inside = true;

		for(int i = 0; i < 6; ++i)
		{
			REAL dist = m_planes[i].DotCoord(bbox.GetCenter());

			Vector3<REAL> half = bbox.GetHalfSize();
			REAL maxAbsDist = fabs(m_planes[i].a * half.x) + fabs(m_planes[i].b * half.y) + fabs(m_planes[i].c * half.z);

			if (dist < -maxAbsDist) //negative
				return NONE;
			else if (dist > +maxAbsDist) //positive
				continue;
			else //both
				all_inside = false;
		}
		if ( all_inside )
			return FULL;
		else
			return PARTIAL;
	}
	/**********************************************************************************************************/
	void Camera::RotateX(REAL angle)
	{
		Matrix4<REAL> matr;
		matr.MakeRotFromAxis(m_right,angle*(PI/180));
		m_dir = matr*m_dir;
		m_dir.Normalise();
		LookAt();
	}
	/**********************************************************************************************************/
	void Camera::RotateY(REAL angle)
	{
		Matrix4<REAL> matr;
		matr.MakeRotY(angle*(PI/180));
		m_right = matr*m_right;
		m_right.Normalise();
		m_dir = matr*m_dir;
		m_dir.Normalise();
		LookAt();
	}
	/**********************************************************************************************************/
	void Camera::Move(REAL distance)
	{
		m_position += (m_dir*distance);
		LookAt();
	}
	/**********************************************************************************************************/
	void Camera::Strafe(REAL distance)
	{
		m_position += (m_right*distance);
		LookAt();
	}
	/**********************************************************************************************************/
	void Camera::EnableDebug(bool val)
	{
		m_debugMode = val;
		m_renderable->Update();
	}
	/**********************************************************************************************************/
	void Camera::UpdateRenderQueue(RenderQueue* queue)const
	{
		if(m_debugMode)
		{
			queue->AddRenderable(m_renderable);
		}
	}
	/**********************************************************************************************************/

	/**********************************************************************************************************/
	/*
				 1*-----*2
				_/|     |
			  _/  |     |
			_/   3*-----*4
           /
		  *0
	*/
	Camera::CameraRenderable::CameraRenderable(Camera* camera)
		:m_camera(camera)
	{
		WeakUtils::UnsafeCopy(m_material, ResourceManager::GetSingleton().Create("debugcamera.material", "Material"));
		m_material->Load();


		m_vertexDeclaration = Graphic::GetSingleton().GetRenderSystem()->CreateVertexDeclaration();
		m_vertexDeclaration->AddElement(0, 0, VET_FLOAT3, VES_POSITION);

		m_indexData = Graphic::GetSingleton().GetRenderSystem()->CreateIndexBuffer(6, INDEX_BUFFER_16);
		unsigned short* indexDataPtr = reinterpret_cast<unsigned short*>(m_indexData->Lock());
		indexDataPtr[0] = 0;
		indexDataPtr[1] = 3;
		indexDataPtr[2] = 1;

		indexDataPtr[3] = 0;
		indexDataPtr[4] = 1;
		indexDataPtr[5] = 2;

		indexDataPtr[6] = 0;
		indexDataPtr[7] = 2;
		indexDataPtr[8] = 4;

		indexDataPtr[9] = 0;
		indexDataPtr[10] = 3;
		indexDataPtr[11] = 4;

		indexDataPtr[12] = 3;
		indexDataPtr[13] = 2;
		indexDataPtr[14] = 1;

		indexDataPtr[15] = 3;
		indexDataPtr[16] = 4;
		indexDataPtr[17] = 2;

		m_indexData->UnLock();

		m_vertexData = Graphic::GetSingleton().GetRenderSystem()->CreateVertexBuffer(5, m_vertexDeclaration->GetVertexSize(0));

		m_renderData = Graphic::GetSingleton().GetRenderSystem()->CreateRenderData(6, 5,m_vertexDeclaration,m_indexData,m_vertexData);
	}
	/**********************************************************************************************************/
	Camera::CameraRenderable::~CameraRenderable()
	{
		if(!m_material.IsNull())
		{
			std::string name = m_material->GetName();
			m_material.Delete();
			ResourceManager::GetSingleton().TryRemove(name);
		}
	}
	/**********************************************************************************************************/
	const MaterialPtr Camera::CameraRenderable::GetMaterial(void) const
	{
		return m_material;
	}
	/**********************************************************************************************************/
	RenderDataPtr Camera::CameraRenderable::GetRenderData() const
	{
		return m_renderData;
	}
	/**********************************************************************************************************/
	const Matrix4<REAL>& Camera::CameraRenderable::GetWorldTransform() const
	{
		return m_transform;
	}
	/**********************************************************************************************************/
	void Camera::CameraRenderable::Update()
	{
		m_transform = m_camera->m_viewMatrix.Inverse() * m_camera->m_projMatrix.Inverse();

		struct Vertex
		{
			float x;
			float y;
			float z;
		};
		Vertex* vertexDataPtr = static_cast<Vertex*>(m_vertexData->Lock());

		vertexDataPtr[0].x = 0;
		vertexDataPtr[0].y = 0;
		vertexDataPtr[0].z = 0;

		vertexDataPtr[1].x = 1;
		vertexDataPtr[1].y = 1;
		vertexDataPtr[1].z = 1;

		vertexDataPtr[2].x = -1;
		vertexDataPtr[2].y = 1;
		vertexDataPtr[2].z = 1;

		vertexDataPtr[3].x = 1;
		vertexDataPtr[3].y = -1;
		vertexDataPtr[3].z = 1;

		vertexDataPtr[4].x = -1;
		vertexDataPtr[4].y = -1;
		vertexDataPtr[4].z = 1;

/*
		vertexDataPtr[0].x = 0;
		vertexDataPtr[0].y = 0;
		vertexDataPtr[0].z = -300;

		vertexDataPtr[1].x = 300;
		vertexDataPtr[1].y = 300;
		vertexDataPtr[1].z = 300;

		vertexDataPtr[2].x = -300;
		vertexDataPtr[2].y = 300;
		vertexDataPtr[2].z = 300;

		vertexDataPtr[3].x = 300;
		vertexDataPtr[3].y = -300;
		vertexDataPtr[3].z = 300;

		vertexDataPtr[4].x = -300;
		vertexDataPtr[4].y = -300;
		vertexDataPtr[4].z = 300;
*/
		m_vertexData->UnLock();
	}
	/**********************************************************************************************************/
	const HardWareBufferPtr Camera::CameraRenderable::GetVertexData() const
	{
		return m_vertexData;
	}
	/**********************************************************************************************************/
	const HardWareBufferPtr Camera::CameraRenderable::GetIndexData() const
	{
		return m_indexData;
	}
	/**********************************************************************************************************/
	const VertexDeclarationPtr Camera::CameraRenderable::GetVertexDeclaration() const
	{
		return m_vertexDeclaration;
	}
	/**********************************************************************************************************/
}
